(ns rete.rule
  (:require [clojure.string :as string])
  (:require [rete.predicate :refer :all])
  (:require [rete.core :refer :all])
  (:require [rete.environment :refer :all :as rete-graph])
  (:require [rete.node :refer :all :as rete-node])
  (:import [rete.core Graph Rule]))

(defn create-new-Rule
  "(create-new-Rule predicates implication) creates a new rule with the given name, predicates and implication"
  [predicates implication]
  {:pre [(coll? predicates)]}
  (Rule. (string/join [(string/join " ∧ " (map #(:name %1) predicates)) 
                      " => "
                      (:name implication)]) 
         (reduce #(assoc %1 (:name %2) %2) {} predicates)
         implication))

(defn- add-rule-RETE-1 [^Graph graph ^Rule rule]
  (let [betanode (rete-node/new-β-node)
        nodes (set (keys (:predicates rule)))]
    (add-child-to-parent 
      (add-parents-to-child 
        (add-root-node graph betanode) 
        nodes (:name betanode))
      (:name (:implication rule)) (:name betanode))))

(defn- extract-nodes [rules]
  (reduce #(merge %1 (merge (:predicates %2) 
                            {(:name (:implication %2)) (:implication %2)})) 
          {} rules))

(defn- parents? [node]
  (not-empty (:parents node)))

(defn- adapt-parents [^Graph graph]
  (reduce #(add-parents-to-child %1 (:parents %2) (:name %2)) graph
          (filter parents? (vals (:nodes graph)))))

(defn create-RETE-1
  "(create-graph rules) creates a new RETE-1 Graph on the basis of the passed rules."
  [rules]
  {:pre [(coll? rules)]}
  (reduce add-rule-RETE-1 
          (adapt-parents 
            (reduce add-root-node 
                    (rete-graph/create-new-graph) 
                    (vals (extract-nodes rules)))) 
          rules))
